package com.neuronbit.businessflow.intellij.editor.swing.editor.propertyInspector;

import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.ui.ComboBox;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiField;
import com.intellij.psi.PsiModifier;
import com.intellij.ui.SimpleListCellRenderer;
import com.intellij.util.concurrency.AppExecutorUtil;
import com.intellij.util.ui.UIUtil;
import com.mxgraph.swing.RadEdgeComponent;
import com.neuronbit.businessflow.intellij.editor.swing.editor.FormEditingUtil;
import com.neuronbit.businessflow.intellij.model.FormattedMethodString;
import com.neuronbit.businessflow.intellij.editor.swing.editor.RadComponent;
import com.neuronbit.businessflow.intellij.editor.swing.editor.propertyInspector.renderers.LabelPropertyRenderer;
import com.neuronbit.businessflow.intellij.model.Decision;
import com.neuronbit.businessflow.intellij.model.SwitchTask;
import org.apache.commons.lang3.NotImplementedException;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * The type Edge label property.
 */
public class EdgeLabelProperty extends Property<RadEdgeComponent, String> {

    private final LabelPropertyRenderer<String> renderer;
    private StringPropertyEditor editor;

    /**
     * Instantiates a new Edge label property.
     */
    public EdgeLabelProperty() {
        super(null, "label");
        renderer = new LabelPropertyRenderer<>();
        editor = new StringPropertyEditor();
    }

    @Override
    public String getValue(RadEdgeComponent component) {
        return component.getDelegee().getValue() == null ? "" : component.getDelegee().getValue().toString();
    }

    @Override
    protected void setValueImpl(RadEdgeComponent component, String value) throws Exception {
        component.setValue(value);
    }

    @Override
    public @NotNull PropertyRenderer<String> getRenderer() {
        return renderer;
    }

    @Override
    public @Nullable PropertyEditor<String> getEditor() {
        return editor;
    }

    private static class StringPropertyEditor extends PropertyEditor<String> {
        /**
         * The My tf.
         */
        protected final JTextField myTf;
        /**
         * The My cbx.
         */
        protected final ComboBox<String> myCbx;
        private int type = 0;

        private StringPropertyEditor() {
            myTf = new JTextField();
            myTf.addActionListener(new MyActionListener());

            myCbx = new ComboBox<>(-1);
            myCbx.setBorder(null);
            myCbx.addPopupMenuListener(new MyPopupMenuListener());
        }

        @Override
        public @Nullable String getValue() throws Exception {
            if (type == 0) {
                return myTf.getText();
            } else {
                if (myCbx.isEditable()) {
                    final Component editorComponent = myCbx.getEditor().getEditorComponent();
                    //noinspection unchecked
                    return ((JTextField) editorComponent).getText();
                } else {
                    //noinspection unchecked
                    return (String) myCbx.getSelectedItem();
                }
            }
        }

        @Override
        public void updateUI() {
            if (type == 0) {
                SwingUtilities.updateComponentTreeUI(myTf);
            } else {
                SwingUtilities.updateComponentTreeUI(myCbx);
                final ListCellRenderer renderer = myCbx.getRenderer();
                if (renderer instanceof JComponent) {
                    SwingUtilities.updateComponentTreeUI((JComponent) renderer);
                }
            }
        }

        /**
         * Sets value from component.
         *
         * @param ignored the ignored
         * @param value   the value
         */
        protected void setValueFromComponent(RadComponent ignored, String value) {
            myTf.setText(value == null ? "" : value.toString());
        }

        @Override
        public JComponent getComponent(final RadComponent component, final String value, final InplaceContext inplaceContext) {
            if (component.getDelegee().getSource() != null) {
                Object value1 = component.getDelegee().getSource().getValue();
                if (value1 instanceof Decision) {
                    myCbx.setModel(new DefaultComboBoxModel<>(new String[]{"True", "False"}));
                } else if (value1 instanceof SwitchTask) {
                    String returnType =  ((SwitchTask) value1).getCallable().getMethod().getReturnType();
                    if (StringUtils.isNotBlank(returnType)) {
                        final PsiClass aClass;

                        try {
                            aClass = ReadAction.nonBlocking(() -> FormEditingUtil.findClassToBind(component.getModule(), returnType)).submit(
                                    AppExecutorUtil.getAppExecutorService()).get();
                        } catch (Exception e) {
                            throw new RuntimeException(e);
                        }
                        if (null != aClass && aClass.isEnum()) {
                            PsiField[] fields = aClass.getFields();
                            List<String> list = Arrays.stream(fields)
                                    .filter(field -> field.hasModifierProperty(PsiModifier.PUBLIC)
                                            && field.hasModifierProperty(PsiModifier.STATIC)
                                            && field.hasModifierProperty(PsiModifier.FINAL))
                                    .map(PsiField::getName)
                                    .toList();
                            list = new ArrayList<>(list);
                            list.add("DEFAULT");
                            myCbx.setModel(new DefaultComboBoxModel<>(list.toArray(new String[0])));
                        }
                    }
                } else {
                    throw new NotImplementedException();
                }
                myCbx.setRenderer(SimpleListCellRenderer.create("True", s -> s));
                myCbx.setSelectedItem(value);
                type = 1;
                return myCbx;
            } else {
                //
                setValueFromComponent(component, value);

                if (inplaceContext != null) {
                    myTf.setBorder(UIUtil.getTextFieldBorder());
                    if (inplaceContext.isStartedByTyping()) {
                        myTf.setText(Character.toString(inplaceContext.getStartChar()));
                    }
                } else {
                    myTf.setBorder(BorderFactory.createEmptyBorder(0, 4, 0, 0));
                }
                type = 0;
                return myTf;
            }
        }

        /**
         * The type My action listener.
         */
        protected final class MyActionListener implements ActionListener {
            @Override
            public void actionPerformed(final ActionEvent e) {
                fireValueCommitted(true, false);
            }
        }

        private final class MyPopupMenuListener implements PopupMenuListener {
            private boolean myCancelled;

            @Override
            public void popupMenuWillBecomeVisible(final PopupMenuEvent e) {
                myCancelled = false;
            }

            @Override
            public void popupMenuWillBecomeInvisible(final PopupMenuEvent e) {
                if (!myCancelled) {
                    fireValueCommitted(true, true);
                }
            }

            @Override
            public void popupMenuCanceled(final PopupMenuEvent e) {
                myCancelled = true;
            }
        }
    }
}
